#include <memory>
#include <stack>
#include <vector>

#include <logger.h>

class Calcer {
public:
    template<typename T>
    Calcer &operator <<(const T &that)
    {
        LOG2SO(DEBUG) << "T";
        AddSum(sizeof(that));
        return *this;
    }

    template<class T>
    Calcer &operator <<(const std::vector<T> &vec)
    {
        LOG2SO(DEBUG) << "std::vector<T>";
        AddSum(24);
        if (vec.capacity() != 0) {
            AddSum(16);
            AddSum(sizeof(T) * (vec.capacity() - vec.size()));
            for (const auto &v : vec) {
                *this << v;
            }
        }
        return *this;
    }

    template<class T>
    Calcer &operator <<(const std::shared_ptr<T> &ptr)
    {
        LOG2SO(DEBUG) << "std::shared_ptr<T>";
        AddSum(sizeof(ptr));
        if (ptr != nullptr) {
            *this << *ptr;
        }
        return *this;
    }

    int GetSum() const
    {
        return sum_;
    }

private:
    void AddSum(int sum)
    {
        VALUE_LOGGER(sum);
        sum_ += mul_ * sum;
    }

    class ScopedMul {
    public:
        ScopedMul(Calcer &calcer, int mul)
            : calcer_(calcer), mul_(mul)
        {
            calcer_.mul_ *= mul_;
        }

        ~ScopedMul()
        {
            calcer_.mul_ /= mul_;
        }

    private:
        Calcer &calcer_;
        int mul_;
    };

    int sum_ = 0;
    int mul_ = 1;
};

struct MyData {
    int i;
    double d;
    char c;
};

struct MyExternalData {
    std::shared_ptr<std::vector<struct MyData>> mydatas;
};

Calcer &operator <<(Calcer &calcer, const struct MyExternalData &data)
{
    LOG2SO(DEBUG) << "struct MyExternalData";
    calcer << data.mydatas;
    return calcer;
}

int main()
{
    std::vector<int> ints = {{}, {}, {}};
    VALUE_LOGGER((Calcer() << ints).GetSum());
    // std::vector<T>
    // sum: '24'
    // sum: '16'
    // sum: '0'
    // T
    // sum: '4'
    // T
    // sum: '4'
    // T
    // sum: '4'
    // (Calcer() << ints).GetSum(): '52'

    std::vector<MyData> datas = {{}, {}, {}};
    VALUE_LOGGER((Calcer() << datas).GetSum());
    // std::vector<T>
    // sum: '24'
    // sum: '16'
    // sum: '0'
    // T
    // sum: '24'
    // T
    // sum: '24'
    // T
    // sum: '24'
    // (Calcer() << datas).GetSum(): '112'

    std::vector<std::shared_ptr<MyData>> pdatas = {};
    pdatas.push_back(nullptr);
    pdatas.push_back(std::make_shared<MyData>());
    pdatas.push_back(nullptr);
    VALUE_LOGGER((Calcer() << pdatas).GetSum());
    // std::vector<T>
    // sum: '24'
    // sum: '16'
    // sum: '16'
    // std::shared_ptr<T>
    // sum: '16'
    // std::shared_ptr<T>
    // sum: '16'
    // T
    // sum: '24'
    // std::shared_ptr<T>
    // sum: '16'
    // (Calcer() << pdatas).GetSum(): '128'

    std::vector<std::shared_ptr<MyExternalData>> pedatas = {};
    pedatas.push_back(nullptr);
    auto edata = std::make_shared<MyExternalData>();
    pedatas.push_back(edata);
    pedatas.push_back(nullptr);
    edata->mydatas = std::make_shared<std::vector<struct MyData>>();
    edata->mydatas->emplace_back();
    edata->mydatas->emplace_back();
    VALUE_LOGGER((Calcer() << pedatas).GetSum());
    // std::vector<T>
    // sum: '24'
    // sum: '16'
    // sum: '16'
    // std::shared_ptr<T>
    // sum: '16'
    // std::shared_ptr<T>
    // sum: '16'
    // struct MyExternalData
    // std::shared_ptr<T>
    // sum: '16'
    // std::vector<T>
    // sum: '24'
    // sum: '16'
    // sum: '0'
    // T
    // sum: '24'
    // T
    // sum: '24'
    // std::shared_ptr<T>
    // sum: '16'
    // (Calcer() << pedatas).GetSum(): '208'

    return 0;
}
